package analyzer

import (
	"testing"

	malysisv1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/malysis/v1"
	"github.com/stretchr/testify/assert"

	"github.com/safedep/vet/pkg/models"
)

func TestNewMalwareAnalyzerHasMinimumConfidence(t *testing.T) {
	cases := []struct {
		name              string
		config            MalwareAnalyzerConfig
		expectedConfLevel malysisv1.Report_Evidence_Confidence
		reports           []struct {
			confidence malysisv1.Report_Evidence_Confidence
			expected   bool
		}
		wantError bool
	}{
		{
			name:              "when minimum confidence is not set",
			config:            MalwareAnalyzerConfig{},
			expectedConfLevel: malysisv1.Report_Evidence_CONFIDENCE_HIGH,
			reports: []struct {
				confidence malysisv1.Report_Evidence_Confidence
				expected   bool
			}{
				{malysisv1.Report_Evidence_CONFIDENCE_MEDIUM, false},
				{malysisv1.Report_Evidence_CONFIDENCE_UNSPECIFIED, false},
				{malysisv1.Report_Evidence_CONFIDENCE_LOW, false},
				{malysisv1.Report_Evidence_CONFIDENCE_HIGH, true},
			},
		},
		{
			name:              "when minimum confidence is set to HIGH",
			config:            MalwareAnalyzerConfig{MinimumConfidence: "HIGH"},
			expectedConfLevel: malysisv1.Report_Evidence_CONFIDENCE_HIGH,
			reports: []struct {
				confidence malysisv1.Report_Evidence_Confidence
				expected   bool
			}{
				{malysisv1.Report_Evidence_CONFIDENCE_UNSPECIFIED, false},
				{malysisv1.Report_Evidence_CONFIDENCE_LOW, false},
				{malysisv1.Report_Evidence_CONFIDENCE_MEDIUM, false},
				{malysisv1.Report_Evidence_CONFIDENCE_HIGH, true},
			},
		},
		{
			name:              "when minimum confidence is set to MEDIUM",
			config:            MalwareAnalyzerConfig{MinimumConfidence: "MEDIUM"},
			expectedConfLevel: malysisv1.Report_Evidence_CONFIDENCE_MEDIUM,
			reports: []struct {
				confidence malysisv1.Report_Evidence_Confidence
				expected   bool
			}{
				{malysisv1.Report_Evidence_CONFIDENCE_LOW, false},
				{malysisv1.Report_Evidence_CONFIDENCE_UNSPECIFIED, false},
				{malysisv1.Report_Evidence_CONFIDENCE_MEDIUM, true},
				{malysisv1.Report_Evidence_CONFIDENCE_HIGH, true},
			},
		},
		{
			name:      "when invalid minimum confidence is set",
			config:    MalwareAnalyzerConfig{MinimumConfidence: "INVALID"},
			wantError: true,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			analyzer, err := NewMalwareAnalyzer(tc.config)
			if tc.wantError {
				assert.Error(t, err)
				assert.Nil(t, analyzer)
				return
			}

			assert.NoError(t, err)
			assert.Equal(t, tc.expectedConfLevel, analyzer.config.minimumConfidenceLevel)

			for _, report := range tc.reports {
				assert.Equal(t, report.expected, analyzer.hasMinimumConfidence(&malysisv1.Report{
					Inference: &malysisv1.Report_Inference{
						Confidence: report.confidence,
					},
				}))
			}
		})
	}
}

func TestMalwareAnalyzerDecision(t *testing.T) {
	pkgDetail := models.NewPackageDetail(models.EcosystemNpm, "test", "1.0.0")
	pkgManifest := models.NewPackageManifestFromLocal("test", models.EcosystemNpm)

	cases := []struct {
		name   string
		config MalwareAnalyzerConfig
		pkg    *models.Package
		assert func(*models.Package)
	}{
		{
			name:   "no malware analysis result",
			config: MalwareAnalyzerConfig{},
			pkg: &models.Package{
				Manifest:       pkgManifest,
				PackageDetails: pkgDetail,
			},
			assert: func(pkg *models.Package) {
				assert.Nil(t, pkg.GetMalwareAnalysisResult(), "should not have malware analysis result")
			},
		},
		{
			name:   "when malware with verification record",
			config: MalwareAnalyzerConfig{},
			pkg: &models.Package{
				Manifest:       pkgManifest,
				PackageDetails: pkgDetail,
				MalwareAnalysis: &models.MalwareAnalysisResult{
					Report: &malysisv1.Report{
						Inference: &malysisv1.Report_Inference{
							IsMalware: true,
						},
					},
					VerificationRecord: &malysisv1.VerificationRecord{
						IsMalware: true,
					},
				},
			},
			assert: func(pkg *models.Package) {
				assert.True(t, pkg.GetMalwareAnalysisResult().IsMalware, "should be malware")
			},
		},
		{
			name:   "when malware without verification record",
			config: MalwareAnalyzerConfig{},
			pkg: &models.Package{
				Manifest:       pkgManifest,
				PackageDetails: pkgDetail,
				MalwareAnalysis: &models.MalwareAnalysisResult{
					Report: &malysisv1.Report{
						Inference: &malysisv1.Report_Inference{
							IsMalware: true,
						},
					},
				},
			},
			assert: func(pkg *models.Package) {
				assert.False(t, pkg.GetMalwareAnalysisResult().IsMalware, "should not be malware")
				assert.True(t, pkg.GetMalwareAnalysisResult().IsSuspicious, "should be suspicious")
			},
		},
		{
			name:   "when malware without verification record and trusted analysis",
			config: MalwareAnalyzerConfig{TrustAutomatedAnalysis: true},
			pkg: &models.Package{
				Manifest:       pkgManifest,
				PackageDetails: pkgDetail,
				MalwareAnalysis: &models.MalwareAnalysisResult{
					Report: &malysisv1.Report{
						Inference: &malysisv1.Report_Inference{
							Confidence: malysisv1.Report_Evidence_CONFIDENCE_HIGH,
							IsMalware:  true,
						},
					},
				},
			},
			assert: func(pkg *models.Package) {
				assert.True(t, pkg.GetMalwareAnalysisResult().IsMalware, "should be malware")
			},
		},
		{
			name:   "when malware without verification record and trusted analysis with low confidence",
			config: MalwareAnalyzerConfig{TrustAutomatedAnalysis: true},
			pkg: &models.Package{
				Manifest:       pkgManifest,
				PackageDetails: pkgDetail,
				MalwareAnalysis: &models.MalwareAnalysisResult{
					Report: &malysisv1.Report{
						Inference: &malysisv1.Report_Inference{
							Confidence: malysisv1.Report_Evidence_CONFIDENCE_LOW,
							IsMalware:  true,
						},
					},
				},
			},
			assert: func(pkg *models.Package) {
				assert.False(t, pkg.GetMalwareAnalysisResult().IsMalware, "should not be malware")
				assert.True(t, pkg.GetMalwareAnalysisResult().IsSuspicious, "should be suspicious")
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			a, err := NewMalwareAnalyzer(tc.config)
			assert.NoError(t, err)

			err = a.applyMalwareDecision(tc.pkg)
			assert.NoError(t, err)
			tc.assert(tc.pkg)
		})
	}
}

func TestMalwarePolicyViolationTrigger(t *testing.T) {
	pkgDetail := models.NewPackageDetail(models.EcosystemNpm, "test", "1.0.0")

	cases := []struct {
		name                    string
		config                  MalwareAnalyzerConfig
		pkg                     *models.Package
		expectedPolicyViolation bool
	}{
		{
			name:                    "no malware analysis result",
			expectedPolicyViolation: false,
			config:                  MalwareAnalyzerConfig{},
			pkg: &models.Package{
				PackageDetails: pkgDetail,
			},
		},
		{
			name:                    "when malware with verification record",
			expectedPolicyViolation: true,
			config:                  MalwareAnalyzerConfig{},
			pkg: &models.Package{
				PackageDetails: pkgDetail,
				MalwareAnalysis: &models.MalwareAnalysisResult{
					Report: &malysisv1.Report{
						Inference: &malysisv1.Report_Inference{
							IsMalware: true,
						},
					},
					VerificationRecord: &malysisv1.VerificationRecord{
						IsMalware: true,
					},
				},
			},
		},
		{
			name:                    "when malware without verification record",
			expectedPolicyViolation: false,
			config:                  MalwareAnalyzerConfig{},
			pkg: &models.Package{
				PackageDetails: pkgDetail,
				MalwareAnalysis: &models.MalwareAnalysisResult{
					Report: &malysisv1.Report{
						Inference: &malysisv1.Report_Inference{
							IsMalware: true,
						},
					},
				},
			},
		},
		{
			name:                    "when malware without verification record and trusted analysis",
			expectedPolicyViolation: true,
			config:                  MalwareAnalyzerConfig{TrustAutomatedAnalysis: true},
			pkg: &models.Package{
				PackageDetails: pkgDetail,
				MalwareAnalysis: &models.MalwareAnalysisResult{
					Report: &malysisv1.Report{
						Inference: &malysisv1.Report_Inference{
							Confidence: malysisv1.Report_Evidence_CONFIDENCE_HIGH,
							IsMalware:  true,
						},
					},
				},
			},
		},
		{
			name:                    "when malware without verification record and trusted analysis with low confidence",
			expectedPolicyViolation: false,
			config:                  MalwareAnalyzerConfig{TrustAutomatedAnalysis: true},
			pkg: &models.Package{
				PackageDetails: pkgDetail,
				MalwareAnalysis: &models.MalwareAnalysisResult{
					Report: &malysisv1.Report{
						Inference: &malysisv1.Report_Inference{
							Confidence: malysisv1.Report_Evidence_CONFIDENCE_LOW,
							IsMalware:  true,
						},
					},
				},
			},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			a, err := NewMalwareAnalyzer(tc.config)
			assert.NoError(t, err)

			pkgManifest := models.NewPackageManifestFromLocal("test", models.EcosystemNpm)

			// add this package in the manifest
			pkgManifest.Packages = append(pkgManifest.Packages, tc.pkg)
			tc.pkg.Manifest = pkgManifest

			// verify only one package for testing policy violation
			assert.Equal(t, len(pkgManifest.Packages), 1)

			policyViolation := false
			err = a.Analyze(pkgManifest, func(event *AnalyzerEvent) error {
				if event.Type == ET_FilterExpressionMatched {
					policyViolation = true
				}
				return nil
			})

			assert.NoError(t, err)
			assert.Equal(t, tc.expectedPolicyViolation, policyViolation)
		})
	}
}
